﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Text;
using System.Threading;
using IdeaNMR.Web.Core;
using IdeaNMR.Shared;

using AjaxControlToolkit;
using System.Collections;
using System.Xml.Serialization;
using System.IO;

using System.Xml.Linq;

using System.Text.RegularExpressions;


namespace IdeaNMR.Web
{
    /// <summary>
    /// 
    /// </summary>
    /// <remarks>
    /// <para>
    ///     Validation:
    ///     Default validation group is "DefaultGroup", the buttons that cause validation of this group may have CausesValidation=true
    ///     All other groups must be explicitly called for validation in the handler.
    /// </para>
    /// </remarks>
    public partial class DesignPulseProgram : DesignNMRObject_BasePage<PulseProgram>
    {
        protected override string PageTitle
        {
            get
            {
                return this.Master.Title;
            }
            set
            {
                this.Master.Title = value;
            }
        }

        protected PulseProgramFacade PulseProgramInfo
        {
            get
            {
                return this.EntityFacade as PulseProgramFacade;
            }
            set
            {
                this.EntityFacade = value;
            }
        }
        
        // Used to timetag all the data (because during execution the time will slightly change
        // and this might lead that different steps taged with different time
        // Will be hard to relate those
        private DateTime ActionTime { get; set; }

        protected Guid PageID
        {
            get
            {
                Guid? res = ViewState["PageID"] as Guid?;
                if (!res.HasValue)
                    ViewState["PageID"] = res = Guid.NewGuid();
                return res.Value;
            }
            set
            {
                ViewState["PageID"] = value;
            }
        }

        // Holds the Log for various actions
        protected string LogString { get; set; }

        /// <summary>
        /// Sample in the generate panel.
        /// </summary>
        // ALISTAIR: comment added 09/07/2011
        // Make sample a property of the class
        // The IdeaNMR.Web.Core.Sample class
        // has a partial definitions in:
        // IdeaNMRdb.edmx (database table definition)
        // IdeaNMRdb.Custom.cs
        // IdeaNMRdb.Custom.ICopyable.cs
        // IdeaNMRdb.Custom.Equatable.cs        
        protected Sample Sample
        {
            get
            {
                object r = ViewState["Sample"];
                return r as Sample;
            }
            set
            {
                ViewState["Sample"] = value;
            }
        }

        // ALISTAIR: comment added 09/07/2011
        // Make spectrometer a property of the class
        // The IdeaNMR.Web.Core.Spectrometer class
        // has partial definitions in:
        // IdeaNMRdb.edmx (database table definition)
        // IdeaNMRdb.Custom.cs
        // IdeaNMRdb.Custom.ICopyable.cs
        // IdeaNMRdb.Custom.Equatable.cs
        protected Spectrometer Spectrometer
        {
            get
            {
                object r = ViewState["Spectrometer"];
                return r as Spectrometer;
            }
            set
            {
                ViewState["Spectrometer"] = value;
            }
        }

        protected BenchmarksBuilder bBuilder
        {
            get
            {
                return Session[this.PageID + "BenchmarksBuilder"] as BenchmarksBuilder;
            }
            set
            {
                Session[this.PageID + "BenchmarksBuilder"] = value;
            }
        }
        protected List<BenchmarkResult> BenchmarkResults
        {
            get
            {
                return ViewState["BenchmarkResults"] as List<BenchmarkResult>;
            }
            set
            {
                ViewState["BenchmarkResults"] = value;
            }
        }
        protected static int indirectDimensions { get; set; }

        protected readonly string default_spectrometer_cookie = "default_spectrometer_cookie";
        protected readonly string default_sample_cookie = "default_sample_cookie";

        #region Page Life Cycle
        protected override void Page_PreInit(object sender, EventArgs e)
        {
            base.Page_PreInit(sender, e);
            this.ActionTime = DateTime.Now;

        }

        #region Page Validation
        protected void cvSample_OnValidate(object source, ServerValidateEventArgs args)
        {
            args.IsValid = !(this.Sample == null);
        }
        protected void cvSpectrometer_OnValidate(object source, ServerValidateEventArgs args)
        {
            args.IsValid = !(this.Spectrometer == null);
        }
        /// <summary>
        /// User created identifiers must be unique
        /// </summary>
        /// <param name="source"></param>
        /// <param name="args"></param>
        protected void ComponentIdentifiersUnique_Validate(object source, ServerValidateEventArgs args)
        {
            args.IsValid = ComponentsList.IsValid;
        }

        protected void ValidateAnalytics(object source, ServerValidateEventArgs args)
        {
            System.IO.StringReader sR = new System.IO.StringReader(tbAnalytics.Text);
            System.Xml.XmlTextReader xR = new System.Xml.XmlTextReader(sR);

            XmlSerializer xS = new XmlSerializer(typeof(List<BenchmarkSetup>), new XmlRootAttribute("BenchmarkSetups"));
            args.IsValid = xS.CanDeserialize(xR);
        }
        #endregion

        #region Control Events
        #region Selection Tables
        protected void CloseSelectionPanel(object sender, EventArgs e)
        {
            panSelection.Visible = false;
        }

        protected void Table_Selected(object sender, ItemSelectedCommandEventArgs e)
        {
            AuxComponent auc = e.SelectedItem as AuxComponent;
            if (auc == null)
            {
                string msg = string.Format("DesignPulseProgram.aspx; invalid component type.");
                ScrewTurnWikiFacade.LogEntry(msg, ScrewTurn.Wiki.PluginFramework.EntryType.Error, ScrewTurnWikiFacade.Username);
            }
            ComponentsList.AddComponent(auc);

            panSelection.Visible = false;
        }

        protected void SamplesTable_OnSelected(object sender, ItemSelectedCommandEventArgs e)
        {
            this.SetSample((Sample)e.SelectedItem);
            panSelection.Visible = false;

        }

        protected void SpectrometersTable_OnSelected(object sender, ItemSelectedCommandEventArgs e)
        {
            this.SetSpectrometer((Spectrometer)e.SelectedItem);
            panSelection.Visible = false;
        }
        #endregion

        #region Component List
        protected void ComponentsList_AddCommand(object sender, CommandEventArgs e)
        {
            panSelection.Visible = true;
            ComponentType cT = 0;
            try
            {
                cT = (ComponentType)Enum.Parse(typeof(ComponentType), e.CommandArgument.ToString());
            }
            catch (ArgumentException)
            {
                ScrewTurn.Wiki.Log.LogEntry("Wrong Component type.", ScrewTurn.Wiki.PluginFramework.EntryType.Error, ScrewTurnWikiFacade.Username);
            }
            switch (cT)
            {
                case ComponentType.WaveShape:
                    mvTables.ActiveViewIndex = 0;
                    break;
                case ComponentType.Cpd:
                    mvTables.ActiveViewIndex = 1;
                    break;
                case ComponentType.Script:
                    mvTables.ActiveViewIndex = 2;
                    break;
                default:
                    mvTables.ActiveViewIndex = 0;
                    break;
            }
        }
        /// <summary>
        /// Creates two tabs for components content and script
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void ComponentsList_ComponentAddedCommand(object sender, CommandEventArgs e)
        {

            NamedComponent nC = (NamedComponent)e.CommandArgument;
            // Create Content TabPanel
            TabPanel tabPanel = new TabPanel();
            tabPanel.HeaderText = nC.AuxComponent.Name + " content";

            tabPanel.ID = string.Format("tp_{0}_content", nC.Id);

            //Create New TextBox
            TextBox tbCont = new TextBox();
            //tbCont.MaxLength = Constants.ContentLength;
            tbCont.Rows = 20;
            tbCont.Enabled = false;
            tbCont.TextMode = TextBoxMode.MultiLine;
            // tbCont.Font.Size = FontUnit.Parse("14");
            tbCont.Font.Size = new FontUnit("14px");
            tbCont.Width = new Unit("100%");
            tbCont.Text = nC.AuxComponent.Content;
            tbCont.CssClass = "InputField";

            tabPanel.Controls.Add(tbCont);

            tabPanel.Visible = false;
            tcComponents.Tabs.AddAt(tcComponents.Tabs.Count - 1, tabPanel);

            // Create Content TabPanel
            tabPanel = new TabPanel();
            tabPanel.HeaderText = nC.AuxComponent.Name + " script";

            tabPanel.ID = string.Format("tp_{0}_script", nC.Id);

            //Create New TextBox
            tbCont = new TextBox();
            //tbCont.MaxLength = Constants.ContentLength;
            tbCont.Enabled = false;
            tbCont.Rows = 20;
            tbCont.TextMode = TextBoxMode.MultiLine;
            // tbCont.Font.Size = FontUnit.Parse("14");
            tbCont.Font.Size = new FontUnit("14px");
            tbCont.Width = new Unit("100%");
            tbCont.Text = nC.AuxComponent.Script;
            tbCont.CssClass = "InputField";

            tabPanel.Controls.Add(tbCont);

            tabPanel.Visible = false;
            tcComponents.Tabs.AddAt(tcComponents.Tabs.Count - 1, tabPanel);
        }
        /// <summary>
        /// Displays the tabs for selected component
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void ComponentsList_SelectedChangedCommand(object sender, CommandEventArgs e)
        {
            Guid sId = ComponentsList.Selected;

            string contentTabName = string.Format("tp_{0}_content", sId);
            string scriptTabName = string.Format("tp_{0}_script", sId);
            int currentTabIndex = 0; // Will Keep an index of the selected Tab

            bool isOutPutVisible = tpOutput.Visible;

            // Only one extra tab is visible
            foreach (TabPanel tp in tcComponents.Tabs)
            {
                if (tp.ID == contentTabName || tp.ID == scriptTabName)
                {
                    tp.Visible = true;
                    currentTabIndex = tcComponents.Tabs.IndexOf(tp);   // Store the index of Tab
                }
                else { tp.Visible = false; }
            }

            // First 3 tabs must always be visible
            tcComponents.Tabs[0].Visible = true;
            tcComponents.Tabs[1].Visible = true;
            tcComponents.Tabs[2].Visible = true;

            // tpOutput must not change
            tpOutput.Visible = isOutPutVisible;

            // Check if Tab was found
            if (currentTabIndex == 0)   // Means tha Tab was not found.
                throw new ArgumentNullException("currentTapPanel",
                    string.Format("TabPanels with ID '{0}' and '{1}' were not found. ", contentTabName, scriptTabName));

            // Active Tab must be switched to the tab, that just was selected
            tcComponents.ActiveTabIndex = currentTabIndex - 1;
        }
        #endregion

        #region Generate Panel

        // ALISTAIR: Comment added 09/07/2011
        // Generates a macro file for an experiment, using the
        // pulse program, setup parameters, spectrometer and sample
        // selected by the user
        protected void btnGenerate_Click(object sender, EventArgs e)
        {
            // make sure the input parameters are valid
            Page.Validate("DefaultGroup");
            Page.Validate("GenerateGroup");
            if (!Page.IsValid) return;

            // Retrieve info from the web form
            this.CollectFormData();
            // Save the current web form details as a Draft.
            // This data will then be used to build the experiment
            this.SaveData(true);

            // Prepare Log Output
            // Make the "Output" tab panel visible
            tpOutput.Visible = true;
            // get the number of active tabs in the tab container 
            tcComponents.ActiveTabIndex = tcComponents.Tabs.Count - 1;
            // re-initialise the log
            this.LogString = string.Empty;

            // Create and initialize ExperimentBuilder
            // from the IdeaNMR.Shared.ExperimentBuilder class
            ExperimentBuilder eBuilder = new ExperimentBuilder();

            // Initialise the values for the experiment builder
            // "Script" and "Name" are properties of IdeaNMR.Web.Core.BaseObject class,
            // "Content" is a property of IdeaNMR.Web.Core.NMRObject class,
            // both defined in IdeaNMRdb.Designer.cs
            eBuilder.SpectrometerScript = this.Spectrometer.Script;
            eBuilder.SampleScript = this.Sample.Script;
            eBuilder.PulseProgramContent = this.PulseProgramInfo.EntityObject.Content;
            eBuilder.PulseProgramScript = this.PulseProgramInfo.EntityObject.Script;
            eBuilder.PulseProgramFileName = this.PulseProgramInfo.EntityObject.Name;

            // Create list of user parameters
            // gets the text from the Parameters text box
            // Each set of parameters is separated by a semicolon (;),
            // with each parameter of the form "key = value"
            Dictionary<string, string> parameters = new Dictionary<string, string>();
            string[] pars_vals = tbParameters.Text.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
            foreach (string par_val in pars_vals)
            {
                string[] pv = par_val.Split('=');
                string param = pv[0].Trim();
                string val = pv[1].Trim();
                if (parameters.ContainsKey(param))
                {
                    string msg = string.Format("Parameters string contains parameter {0} twice.", param);
                    parameters[param] = val;
                }
                else
                {
                    // Add the key/value pair to the parameters dictionary
                    parameters.Add(param, val);
                }
            }

            // add the parameters dictionary to the experimental builder
            eBuilder.Parameters = parameters;

            // Add all "components" selected by the user
            // i.e. the "Wave Shapes", "" and "composite pulse decouplings (CPDs)"
            // and "scripts"
            foreach (NamedComponent nC in this.ComponentsList.Components)
            {
                EntityType eT = nC.AuxComponent.GetEntityType();

                ComponentItem cI = new ComponentItem()
                {
                    Id = nC.Id,
                    Identifier = nC.Identifier,
                    Name = nC.AuxComponent.Name,
                    Type = eT.ToComponentType(),
                    IsIncludable = eT == EntityType.Script
                                        ? ((Script)nC.AuxComponent).IsIncludable
                                        : false,
                    Script = nC.AuxComponent.Script,
                    Content = nC.AuxComponent.Content
                };
                eBuilder.Components.Add(cI.Identifier, cI);
            }

            // Generate the experiment macro, using the experiment name (input by the user)
            // and the username as input parameters
            string Macro = eBuilder.CreateExperimentScript(tbExperimentName.Text, ScrewTurnWikiFacade.Username);

            // Print log
            while (eBuilder.Log.HasEntries)
            {
                LogBookEntryType lbet;
                int level;
                string msg = eBuilder.Log.ReadEntry(out lbet, out level);
                WriteMessage(string.Empty.PadLeft(3 * level) + msg, lbet);
            }
            // If critical errors happened, stop execution.
            if (Macro == null)
            {
                WriteMessage("The macro file is not formed due to errors in the execution.".ToUpper(), LogBookEntryType.Error);
                return;
            }

            Experiment ex = new Experiment();
            ex.Datetime = this.ActionTime;
            ex.Id = Guid.NewGuid();
            ex.Name = tbExperimentName.Text + ".py";
            ex.Remark = tbRemarkGen.Text;
            ex.Script = Macro;
            ex.Parameters = tbParameters.Text;

            this.PulseProgramInfo.AttachExperiment(ex, this.Spectrometer, this.Sample);

            // On next request it could be attached to another object context.
            if (this.Sample.EntityState != System.Data.EntityState.Detached || this.Spectrometer.EntityState != System.Data.EntityState.Detached)
            {
                string err = string.Format("The Sample or Spectrometer entity objects are attached to the lost object context. Sample: {0}; Spectrometer: {1}.",
                    this.Sample.EntityState.ToString(), this.Spectrometer.EntityState.ToString());
                throw new InvalidOperationException(err);
            }

            linkToDownloadMacro.Text = "Download File: " + tbExperimentName.Text + ".py";
            Session["ReadyScript"] = Macro;
            Session["ReadyName"] = tbExperimentName.Text + ".py";
            linkToDownloadMacro.Visible = true;

            WriteMessage("Operation finished successfully", LogBookEntryType.Checkpoint);

        } // end btnGenerate_Click

        /// <summary>
        /// Opens selection tables on Sample View
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void lbSelectSample_Click(object sender, EventArgs e)
        {
            panSelection.Visible = true;
            mvTables.ActiveViewIndex = 3;
            SamplesTable.Refresh();
        }
        protected void lbSelectSpectrometer_Click(object sender, EventArgs e)
        {
            panSelection.Visible = true;
            mvTables.ActiveViewIndex = 4;
            SpectrometersTable.Refresh();
        }

        private void SetSpectrometer(Spectrometer s)
        {
            if (s == null) return;
            this.Spectrometer = s;

            // Set label on Generate panel
            lbSelectSpectrometer.Text = string.Format("{0}. Field: {1} Location: {2}.", this.Spectrometer.Name, this.Spectrometer.Frequency, this.Spectrometer.Location);
            lbSelectSpectrometer.ToolTip = string.Format("ID: {{{0}}}", this.Spectrometer.Id);
            lbSelectSpectrometer.Font.Bold = true;

            // Set cookie
            if (Request.Cookies[default_spectrometer_cookie] == null ||
                    Request.Cookies[default_spectrometer_cookie].Value != this.Spectrometer.Id.ToString())
            {
                Response.Cookies[default_spectrometer_cookie].Value = this.Spectrometer.Id.ToString();
                Response.Cookies[default_spectrometer_cookie].Expires = DateTime.Now.AddDays(30);
            }
        }

        private void SetSample(Sample s)
        {
            if (s == null) return;
            this.Sample = s;

            lbSelectSample.Text = string.Format("{0} Buffer: {1}; Labeling: {2}.", this.Sample.Name, this.Sample.Buffer, this.Sample.Labeling);
            lbSelectSample.ToolTip = string.Format("ID: {{{0}}}", this.Sample.Id);
            lbSelectSample.Font.Bold = true;
            if (Request.Cookies[default_sample_cookie] == null ||
                    Request.Cookies[default_sample_cookie].Value != this.Sample.Id.ToString())
            {
                Response.Cookies[default_sample_cookie].Value = this.Sample.Id.ToString();
                Response.Cookies[default_sample_cookie].Expires = DateTime.Now.AddDays(30);
            }
        }
        #endregion

        
        #region Benchmarks
        
        // ALISTAIR: COMMENT ADDED 24/01/2011
        // Immediately after benchmark data has been processed,
        // a "Show Benchmarks Data" button will be displayed.
        // When the Benchmarks data is displayed, this button changes
        // to "Hide Benchmarks Data"
        // ALISTAIR 24/05/2011
        // Changed "Benchmarks" to "S/N Prediction" in text label
        protected void ShowHideBenchmarks(object sender, EventArgs e)
        {
            switch (mvContent.ActiveViewIndex)
            {
                case 0:
                    mvContent.ActiveViewIndex = 1;
                    lbtnShowHideBenchmarks.Text = "Hide S/N Prediction Data";
                    //lbtnShowHideBenchmarks.Text = "Hide Benchmarks Data";
                    break;
                case 1:
                    mvContent.ActiveViewIndex = 0;
                    lbtnShowHideBenchmarks.Text = "Show S/N Prediction Data";
                    //lbtnShowHideBenchmarks.Text = "Show Benchmarks Data";
                    break;
                default:
                    mvContent.ActiveViewIndex = 0;
                    lbtnShowHideBenchmarks.Text = "Show S/N Prediction Data";
                    //lbtnShowHideBenchmarks.Text = "Show Benchmarks Data";
                    break;
            }
        }

        // ALISTAIR: ADDED 02/04/2012
        // Add functionality to set the default values for S/N prediction
        // Both TD and SW parameters should come from the pulse program "setup" script
        protected void SetSNDefaults(object sender, EventArgs e)
        {
            // get the pulse program ID, so that the latest experiment details can be retrieved.
            Guid pulseProgID = this.TargetID.Value;

            // re-initialise TD & SW input boxes to an empty string
            this.tbTimeDomains.Text = String.Empty;
            this.tbSpectralWidths.Text = String.Empty;
            this.tbSpectFreq.Text = String.Empty;

            // Read the contents of the "Simulated Interferograms" tab (i.e. input from the Spinach)
            // The contents are contained within the <BenchmarkSetups> XML root tag, which contains a list
            // of individual <BenchmarkSetup> tags, one for each indirect dimension
            // To set the default parameters for analysing S/N, the labels associated with each dimension are
            // required for both the time domain points (TD) and the spectral width (SW)
            System.IO.StringReader sR = new System.IO.StringReader(tbAnalytics.Text);

            XElement tbAnalyticsXML = XElement.Load(sR);

            // prepare XML statement to retreive the attributes of each dimension
            var dimensionDataXML = from dimensionData in tbAnalyticsXML.Elements("BenchmarkSetup")
                              let dimension = dimensionData.Attribute("Dimension").Value
                              group dimensionData by dimension;

            // loop through each dimension of the XML dataa
            foreach (var indirectDimension in dimensionDataXML)
            {
                string dimension = String.Empty;
                string dimPrefix = String.Empty;

                try
                {
                    dimension = indirectDimension.Attributes("Dimension").ElementAt(0).Value;
                }
                catch (Exception)
                {
                    ScrewTurn.Wiki.Log.LogEntry("Error: No dimension attribute associated with the Spinach XML benchmark data.\nPlease make sure that each BenchmarkSetup tag is associated with an indirect dimension for the experiment.", ScrewTurn.Wiki.PluginFramework.EntryType.Error, ScrewTurnWikiFacade.Username);
                }

                try
                {
                    dimPrefix = indirectDimension.Attributes("DimPrefix").ElementAt(0).Value;
                }
                catch (Exception)
                {
                    ScrewTurn.Wiki.Log.LogEntry("Error: No dimesion prefix attribute associated with the Spinach XML benchmark data.\nPlease make sure that each BenchmarkSetup tag contains a DimPrefix attribute for each indirect dimension.", ScrewTurn.Wiki.PluginFramework.EntryType.Error, ScrewTurnWikiFacade.Username);
                }

                string experimentScript;
                string experimentName;
                Guid experimentPulseProg;
                
                // In order to retreive the TD & SW data, an experiment must exist
                // for the current pulse program (specifically, the currently selected
                // version of the pulse program, given by its pulse program ID)
                // If an experiment does not exist, an exception will be thrown when
                // retrieving an sqlResult
                try
                {

                    // Establish a new database connection
                    using (IdeaNMRdbContainer inmr = new IdeaNMRdbContainer())
                    {
                        // Execute SQL query to get the experiment that matches the selected experiment.
                        // Select the experiment macro script and experiment name of the first result (there should only be one).
                        var sqlResult = (from exps in inmr.Experiments
                                         where exps.PulseProgram.Id == pulseProgID
                                         select new
                                         {
                                             Script = exps.Script,
                                             Name = exps.Name,
                                             PulseProg = exps.PulseProgram.Id
                                         }).FirstOrDefault();

                        experimentScript = sqlResult.Script;
                        experimentName = sqlResult.Name;
                        experimentPulseProg = sqlResult.PulseProg;
                    }

                    // extract TD (time domain points), SW (spectral width, in ppm)
                    // & SF (spectral frequency, in Hz) values from the relevant entries
                    // in the experiment script based on the bruker prefix defined for
                    // the current dimension
                    // (e.g. dimension prefix 1 will have format "1 td 2048")
                    // System.Diagnostics.Debug.WriteLine(experimentScript);

                    Regex tdRegEx = new Regex(dimPrefix + @"\s+td\s+(.*)\n");
                    MatchCollection tdRegExMatchList = tdRegEx.Matches(experimentScript);
                    //System.Diagnostics.Debug.WriteLine("TD count from MatchList: " + tdRegExMatchList.Count);

                    foreach (Match aMatch in tdRegExMatchList)
                    {
                        // Get the number of groups (excluding the first group, which is the full match)
                        //System.Diagnostics.Debug.WriteLine("TD no. of groups in current match: " + (aMatch.Groups.Count - 1));

                        // add the value of TD points of this dimension to the default values for the TimeDomains textbox
                        this.tbTimeDomains.Text += dimension + "=" + aMatch.Groups[1].Value + ";";                        
                    }

                    Regex swRegEx = new Regex(dimPrefix + @"\s+sw\s+(.*)\n");
                    MatchCollection swRegExMatchList = swRegEx.Matches(experimentScript);
                    //System.Diagnostics.Debug.WriteLine("SW count from MatchList: " + swRegExMatchList.Count);

                    double spectralWidthInPPM = 0;
                    double spectralWidthInHz = 0;
                    double spectFreq = 0;

                    foreach (Match aMatch in swRegExMatchList)
                    {
                        // Get the number of groups (excluding the first group, which is the full match)
                        //System.Diagnostics.Debug.WriteLine("SW no. of groups in current match: " + (aMatch.Groups.Count - 1));

                        // add the value of SW of this dimension to the default values for the SpectralWidths textbox
                        this.tbSpectralWidths.Text += dimension + "=" + aMatch.Groups[1].Value + ";";
                        spectralWidthInPPM = Double.Parse(aMatch.Groups[1].Value);
                    }

                    Regex sfRegEx = new Regex(dimPrefix + @"\s+sf\s+(.*)\n");
                    MatchCollection sfRegExMatchList = sfRegEx.Matches(experimentScript);
                    //System.Diagnostics.Debug.WriteLine("SF count from MatchList: " + sfRegExMatchList.Count);

                    foreach (Match aMatch in sfRegExMatchList)
                    {
                        // Get the number of groups (excluding the first group, which is the full match)
                        //System.Diagnostics.Debug.WriteLine("SF no. of groups in current match: " + (aMatch.Groups.Count - 1));

                        spectFreq = Double.Parse(aMatch.Groups[1].Value);
                        spectralWidthInHz = spectralWidthInPPM * spectFreq;

                        // add the value of SW of this dimension to the default values for the SpectralWidths textbox
                        this.tbSpectFreq.Text += dimension + "=" + spectFreq + ";";
                    }

                }
                catch (Exception)
                {
                    ScrewTurn.Wiki.Log.LogEntry("Error generating most recent experiment: No experiment is available for the current version of the pulse program.\nBefore attempting to analyse Spinach data, please generate an experiment for the current version of the pulse program.", ScrewTurn.Wiki.PluginFramework.EntryType.Error, ScrewTurnWikiFacade.Username);
                }

            }  // foreach dimension

        } // end SetSNDefaults

        // ALISTAIR: COMMENT ADDED 24/01/2011
        // Toggles between starting and cancelling the processing of Benchmark data
        protected void StartStopBenchmarks(object sender, EventArgs e)
        {
            // Stop if its started
            // ALISTAIR: COMMENT ADDED 24/01/2011
            // If a benchmark builder object already exists, the processing of benchmark data
            // has been started and should be stopped
            // This is initiated if the user clicks "Cancel Benchmarks" once the
            // processing of benchmark data has been initiated
            if (this.bBuilder != null)
            {
                this.bBuilder.Cancel();

                ClearBenchmarks();
                this.timerBenchmarks.Enabled = false;
                // ALISTAIR 24/05/2011
                // Change button name from "Run Benchmarks"
                // to "Predict S/N", likewise with cancellation
                this.btnRunBenchmarks.Text = "Predict S/N";
                this.lblBenchmarkStatus.Text = "S/N prediction canceled on: " + DateTime.Now.ToUsersLocalTime();
                //this.btnRunBenchmarks.Text = "Run Benchmarks";
                //this.lblBenchmarkStatus.Text = "Benchmarks canceled on: " + DateTime.Now.ToUsersLocalTime();
                this.BenchmarkInProgress.Visible = false;
            }
            // RunEverything
            // ALISTAIR: COMMENT ADDED 24/01/2011
            // This is initiated if the user clicks "Predict S/N"
            else
            {
                // ALISTAIR: COMMENT ADDED 24/01/2011
                // use revAnalytics (regular expression validator) to validate input
                Page.Validate("BenchmarksGroup");
                this.revAnalytics.Validate();
                if (!Page.IsValid) return;

                this.bBuilder = new BenchmarksBuilder();

                // ALISTAIR: COMMENT ADDED 24/01/2011
                // Read the contents of the "Analytics" tab (i.e. input from the Spinach transfer function)
                // The contents are contained within the <BenchmarkSetups> XML root tag, which contains a list
                // of individual <BenchmarkSetup> tags, one for each indirect dimension
                // ALISTAIR: 21/05/2011
                // Updated to read <Real> tag for each TauC
                // ALISTAIR: 23/03/2012
                // Updated to include input of <Imag> tag for each TauC

                System.IO.StringReader sR = new System.IO.StringReader(tbAnalytics.Text);
                //XmlSerializer xS = new XmlSerializer(typeof(List<BenchmarkSetup>), new XmlRootAttribute("BenchmarkSetups"));
                //List<BenchmarkSetup> setups = (List<BenchmarkSetup>)xS.Deserialize(sR);

                XElement tbAnalyticsXML = XElement.Load(sR);

                List<BenchmarkSetup> setups = new List<BenchmarkSetup>();

                // ALISTAIR Modified 12/03/2012: Changed orderby clause to order by numeric value,
                // rather than alphabetically
                var realDataXML = from realData in tbAnalyticsXML.Descendants("Real")
                                  let dimension = realData.Parent.Attribute("Dimension").Value
                                  orderby Double.Parse(realData.Attribute("TauC").Value)
                                  group realData by dimension;

                foreach (var indirectDimension in realDataXML)
                {
                    BenchmarkSetup dimensionSetup = new BenchmarkSetup();
                    dimensionSetup.Dimension = indirectDimension.Key;
                    // ALISTAIR 04/04/2012:
                    // Add attribute to each dimension that identifies the Bruker
                    // prefix that's used for each dimension (e.g. prefix of 1 may represent the 15N dimension).
                    // This will be used to retrieve parameters used for calculating S/N statistics,
                    // such as the time domain points (where dimension 1 will become "1 td" parameter),
                    // spectral width (SW, in ppm) &  spectrometer frequency (SF)
                    //dimensionSetup.DimPrefix = indirectDimension.Attributes("DimPrefix").ElementAt(0).Value;

                    foreach (XElement real in indirectDimension)
                    {
                        SimulatedData simData = new SimulatedData();
                        simData.TimeValueRealTable = real.Value;
                        simData.TauC = Double.Parse(real.Attribute("TauC").Value);
                        //dimensionSetup.TDLabel = real.Parent.Attribute("TD").Value;
                        //dimensionSetup.SWLabel = real.Parent.Attribute("SW").Value;
                        dimensionSetup.DimensionData.Add(simData);
                    }

                    setups.Add(dimensionSetup);
                }

                // ALISTAIR Modified 12/03/2012: Changed orderby clause to order by numeric value,
                // rather than be alphabetically
                var imagDataXML = from imagData in tbAnalyticsXML.Descendants("Imag")
                                  let dimension = imagData.Parent.Attribute("Dimension").Value
                                  orderby Double.Parse(imagData.Attribute("TauC").Value)
                                  group imagData by dimension;

                int dimensionCounter = 0;

                foreach (var indirectDimension in imagDataXML)
                {
                    int datasetCounter = 0;

                    foreach (XElement imag in indirectDimension)
                    {
                        setups.ElementAt(dimensionCounter).DimensionData.ElementAt(datasetCounter).TimeValueImagTable = imag.Value;
                        datasetCounter++;
                    }

                    dimensionCounter++;
                }

                indirectDimensions = setups.Count;

                // ALISTAIR: COMMENT ADDED 24/01/2011
                // Read in the Time Domains and Spectral Widths parameters,
                // which accept input from the user via text boxes
                // (one for each indirect dimension)
                Dictionary<string, int> timeDomains = ReadParameters<int>(tbTimeDomains.Text);
                Dictionary<string, double> spectralWidths = ReadParameters<double>(tbSpectralWidths.Text);
                Dictionary<string, double> spectFreq = ReadParameters<double>(tbSpectFreq.Text);
                // ALISTAIR: ADDED 26/03/2012
                // Additional parameter for the number of points used in the frequency domain (SI)
                // (one for each indirect dimension)
                Dictionary<string, int> noOfPointsSI = ReadParameters<int>(tbNoOfPointsSI.Text);

                // ALISTAIR: COMMENT ADDED 24/01/2011
                // Assign input parameters to the appropriate benchmark setup variables
                // (one benchmark setup for each dimension)
                // Increments are calculated as the inverse of the spectral widths
                // If there is no input, use the following defaults:
                // Increments = 0.0003; Time Domains = 200.
                // ALISTAIR 26/03/2012:
                // Added no of points (SI) as a value for each dimension
                // Default to bs.SI = 2048 (this was the original hard-coded value)
                // Added number of transients (NS) to each benchmark setup
                foreach (var bS in setups)
                {
                    // ALISTAIR 24/05/2011
                    // Increment is calculated as 1/Spectral Width
                    bS.Increment = spectralWidths.ContainsKey(bS.Dimension)
                                        ? 1.0 / (spectralWidths[bS.Dimension]*spectFreq[bS.Dimension])
                                        : 0.0003;
                    //bS.Increment = spectralWidths.ContainsKey(bS.Dimension)
                    //                    ? 1.0 / spectralWidths[bS.Dimension]
                    //                    : 0.0003;

                    // ALISTAIR 24/05/2011
                    bS.TimeDomain = timeDomains.ContainsKey(bS.Dimension)
                                        ? timeDomains[bS.Dimension]
                                        : 200;

                    // ALISTAIR 24/05/2011
                    bS.NoOfPointsSI = noOfPointsSI.ContainsKey(bS.Dimension)
                                        ? noOfPointsSI[bS.Dimension]
                                        : 2048;
                    /*
                    if ((Math.Log(bS.NoOfPointsSI, 2) % 1) != 0)
                    {
                        // then not a power of two
                        // return error
                        // set tb to something invalid according to validation expression
                        // then re-validate the expression that's known to be invalid, to induce the appropriate error message
                            tbNoOfPointsSI.Text = "jiberish";
                            revNoOfPointsSI.Validate();
                            response.redirect(request.url.tostring);
                            throw Exception;
                    }
                     */

                    // ALISTAIR 23/01/2011
                    // Read in TauC parameter and save as
                    // property of benchmarksetup object
                    bS.UserTauC = double.Parse(tbTauC.Text);

                    // ALISTAIR 18/08/2011
                    // Read in 1D Signal-To-Noise parameter and save as
                    // property of benchmarksetup object
                    bS.OneDimSigToNoise = double.Parse(tbOneDimSigToNoise.Text);

                    // ALISTAIR 26/03/2012
                    // Read in number of transients (number of scans, NS) parameter
                    // and save as property of benchmarksetup object
                    //bS.NoOfTransients = int.Parse(tbNoOfTransients.Text);
                }

                // ALISTAIR 23/05/2011
                // Also add UserTauC to BenchmarkBuilder, so that it 
                // can be used to compare TauC values
                bBuilder.userTauC = double.Parse(tbTauC.Text);

                // ALISTAIR: COMMENT ADDED 24/01/2011
                // Transfer input data to the benchmark builder object
                // (as an array of BenchmarkSetup objects).
                // Start the processing of the benchmark data
                this.bBuilder.InputData = setups.ToArray();
                this.bBuilder.Start();

                // ALISTAIR: COMMENT ADDED 24/01/2011
                // Start timer (Ajax control) and set GUI objects appropriately
                this.timerBenchmarks.Enabled = true;
                this.btnRunBenchmarks.Text = "Cancel Benchmarks";
                this.lblBenchmarkStatus.Visible = true;
                this.lblBenchmarkStatus.Text = "Benchmarks started on: " + DateTime.Now.ToUsersLocalTime();
                this.BenchmarkInProgress.Visible = true;
            }
        }

        // ALISTAIR: COMMENT ADDED 24/01/2011
        // Timer is executed once per second by OnTick event (Ajax control), see DesignPulseProgram.aspx
        // Once benchmark processing has completed, the GUI objects
        // are modified appropriately and the finish time displayed
        // ALISTAIR 24/05/2011: Changed "Benchmarks" to "S/N Prediction"
        // CALLED BY: Event OnTick() in DesignPulseProgram.aspx
        // CALLING: ClearBenchmarks()
        protected void timerBenchmarks_Tick(object sender, EventArgs e)
        {
            this.BenchmarkResults = this.bBuilder.Results;

            if (this.bBuilder.Terminated)
            {
                this.lblBenchmarkStatus.Visible = true;
                this.lblBenchmarkStatus.Text = "S/N prediction finished on: " + DateTime.Now.ToUsersLocalTime();
                this.btnRunBenchmarks.Text = "Predict S/N";
                this.BenchmarkInProgress.Visible = false;

                this.timerBenchmarks.Enabled = false;
                ClearBenchmarks();
            }
        }

        // ALISTAIR: COMMENT ADDED 24/01/2011
        // CALLED BY: StartStopBenchmarks(), timerBenchmarks_Tick()
        // CALLING: none
        private void ClearBenchmarks()
        {
            Session.Remove(this.PageID + "BenchmarksBuilder");
        }

        // ALISTAIR: COMMENT ADDED 24/01/2011
        // view Benchmarks (vBenchmarks) prerendering displays the benchmark data
        // simply by calling DisplayBenchmarkData()
        // This is initiated after the "Show Benchmarks Data" link is selected
        // CALLED BY: Event OnPreRender() in DesignPulseProgram.aspx
        // CALLING: DisplayBenchmarkData()
        protected void vBenchmarks_PreRender(object sender, EventArgs e)
        {
            DisplayBenchmarkData();
        }
        #endregion
        #endregion


        // ALISTAIR: 06/07/2011
        // Translation to Spinach
        protected void ToSpinach(object sender, EventArgs e)
        {
            // use revAnalytics (regular expression validator) to validate input
            Page.Validate("SpinachGroup");
            this.revAnalytics.Validate();
            if (!Page.IsValid) return;

            //this.PulseProgramInfo.EntityObject.Experiment;
            Guid pulseProgID = this.TargetID.Value;

            // Read the contents of the "Pulse Program" tab (i.e. the bruker pulse program)
            StringReader pulseProgStringReader = new StringReader(tbContent.Text);

            // Read the contents of the "Setup" tab (i.e. the setting for the pulse sequence)
            StringReader setupMacroStringReader = new StringReader(tbScript.Text);

            string experimentScript;
            string experimentName;
            Guid experimentPulseProg;
            string spinachMacro = String.Empty;

            // In order to prepare a Spinach macro, an experiment must exist
            // for the current pulse program (specifically, the currently selected
            // version of the pulse program, given by its pulse program ID)
            // If an experiment does not exist, an exception will be thrown when
            // retrieving an sqlResult
            try
            {

                // Establish a new database connection
                using (IdeaNMRdbContainer inmr = new IdeaNMRdbContainer())
                {
                        // Execute SQL query to get the experiment that matches the selected experiment.
                        // Also ensure that the current user is the owner of the experiment
                        // (access the User table from the experiment then get the Username, g.User.Username).
                        // Select the experiment macro script and experiment name of the first result (there should only be one).
                        var sqlResult = (from exps in inmr.Experiments
                                         where exps.PulseProgram.Id == pulseProgID && exps.User.Username == ScrewTurnWikiFacade.Username
                                         select new
                                         {
                                             Script = exps.Script,
                                             Name = exps.Name,
                                             PulseProg = exps.PulseProgram.Id
                                         }).FirstOrDefault();

                        experimentScript = sqlResult.Script;
                        experimentName = sqlResult.Name;
                        experimentPulseProg = sqlResult.PulseProg;
                }

                // call static method to translate to spinach code
                spinachMacro = SpinachSetup.TranslateToSpinach(pulseProgStringReader, experimentScript);

            }
            catch (Exception)
            {
                spinachMacro = "Error generating Spinach macro: No experiment is available for the current version of the pulse program.\nBefore attempting to generate a Spinach macro, please generate an experiment for the current version of the pulse program.";
                ScrewTurn.Wiki.Log.LogEntry(spinachMacro, ScrewTurn.Wiki.PluginFramework.EntryType.Error, ScrewTurnWikiFacade.Username);
            }

            // Write output formatted for spinach to the "Spinach Macro" tab
            tbSpinachMacro.Text = spinachMacro;
            // Make Spinach Macro the active tab, so the user will see the macro
            tcComponents.ActiveTab = tpSpinachMacro;

            // Write output formatted for spinach as a property of the
            // PulseProgram table in the database           
            try
            {
                // Establish a new database connection
                using (IdeaNMRdbContainer inmr = new IdeaNMRdbContainer())
                {
                    var sqlPulseProgResult = (from pulseprog in inmr.BaseObjects.OfType<PulseProgram>()
                                              where pulseprog.Id == pulseProgID //&& pulseprog.User.Username == ScrewTurnWikiFacade.Username // pulseprog.Authors.??
                                        select pulseprog).FirstOrDefault();

                    sqlPulseProgResult.SpinachMacro = spinachMacro;
                    inmr.SaveChanges();
                    spinachOutputLabel.Text = "Spinach macro generated";                
                }
            }
            catch (Exception ex)
            {
                ScrewTurn.Wiki.Log.LogEntry("Error writing Spinach macro to database.\n" + ex.Message, ScrewTurn.Wiki.PluginFramework.EntryType.Error, ScrewTurnWikiFacade.Username);
            }
            
        } // end ToSpinach


        // ALISTAIR: 25/08/2011
        // Update Spinach Macro from user modifications in textbox
        protected void SpinachMacroUpdate(object sender, EventArgs e)
        {
            // use revAnalytics (regular expression validator) to validate input
            Page.Validate("SpinachGroup");
            this.revAnalytics.Validate();
            if (!Page.IsValid) return;

            //this.PulseProgramInfo.EntityObject.Experiment;
            Guid pulseProgID = this.TargetID.Value;
            string spinachMacro = this.tbSpinachMacro.Text;

            // Make Spinach Macro the active tab, so the user will see the macro
            tcComponents.ActiveTab = tpSpinachMacro;

            try
            {
                using (IdeaNMRdbContainer inmr = new IdeaNMRdbContainer())
                {
                    var sqlPulseProgResult = (from pulseprog in inmr.BaseObjects.OfType<PulseProgram>()
                                              where pulseprog.Id == pulseProgID
                                              select pulseprog).FirstOrDefault();

                    sqlPulseProgResult.SpinachMacro = spinachMacro;
                    inmr.SaveChanges();
                    spinachOutputLabel.Text = "Spinach macro updated successfully";
                }
            }
            catch (Exception ex)
            {
                ScrewTurn.Wiki.Log.LogEntry("Error writing Spinach macro to database.\n" + ex.Message, ScrewTurn.Wiki.PluginFramework.EntryType.Error, ScrewTurnWikiFacade.Username);
            }

        } // end SpinachMacroUpdate


        protected override void Page_PreRenderComplete(object sender, EventArgs e)
        {
            base.Page_PreRenderComplete(sender, e);
            lbtnShowHideBenchmarks.Visible = this.BenchmarkResults != null;

            litOutput.Text = this.LogString;
        }
        #endregion

        protected override void InitiateObject()
        {
            this.PulseProgramInfo = (this.TargetID.HasValue)
                ? new PulseProgramFacade(this.TargetID.Value, ScrewTurnWikiFacade.Username)
                : new PulseProgramFacade(ScrewTurnWikiFacade.Username);
        }

        protected override void SetValidationParameters()
        {
            base.SetValidationParameters();

            tbType.MaxLength = Sanitar.Type.MaxLength;
            rfvType.Text = revType.Text = Sanitar.Type.ErrorMsg;
            revType.ValidationExpression = Sanitar.Type.ValidationRegex;

            rfvDimensionality.Text = Sanitar.Dimensionality.ErrorMsg;

            tbExperimentName.MaxLength = Sanitar.Name.MaxLength;
            rfvNameGen.Text = revNameGen.Text = Sanitar.Name.ErrorMsg;
            revNameGen.ValidationExpression = Sanitar.Name.ValidationRegex;

            tbRemarkGen.MaxLength = Sanitar.Remark.MaxLength;
            rfvRemarkGen.Text = revRemarkGen.Text = Sanitar.Remark.ErrorMsg;
            revRemarkGen.ValidationExpression = Sanitar.Remark.ValidationRegex;

            revParameters.Text = "Usage: name = value; semicolon is obligatory at the end of every pair.";
            revParameters.ValidationExpression = @"^(?:([a-zA-Z]+\w*) *= *((?:[a-zA-Z]+\w*)|(?:-?\d+(?:\.\d+)?)))( *; *(?:([a-zA-Z]+\w*) *= *((?:[a-zA-Z]+\w*)|(?:-?\d+(?:\.\d+)?)))*)+$";

            revAnalytics.Text = Sanitar.Analytics.ErrorMsg;
            tbAnalytics.MaxLength = Sanitar.Analytics.MaxLength;
            revAnalytics.ValidationExpression = Sanitar.Analytics.ValidationRegex;

            // ALISTAIR 25/08/2011: Validate spinach macro text form textbox
            revSpinachMacro.Text = Sanitar.SpinachMacro.ErrorMsg;
            tbSpinachMacro.MaxLength = Sanitar.SpinachMacro.MaxLength;
            revSpinachMacro.ValidationExpression = Sanitar.SpinachMacro.ValidationRegex;

            // ALISTAIR 23/05/2011: Add validation for TauC input
            // rev - regular expression validator
            // rfv - required field validator
            // tb - textbox
            // User parameter needs to be added to the Sanitar class
            revTauC.ValidationExpression = Sanitar.TauC.ValidationRegex;
            rfvTauC.Text = revTauC.Text = Sanitar.TauC.ErrorMsg;
            tbTauC.MaxLength = Sanitar.TauC.MaxLength;

            // ALISTAIR 18/08/2011: Add validation for 1D Signal-To-Noise input
            revOneDimSigToNoise.ValidationExpression = Sanitar.OneDimSigToNoise.ValidationRegex;
            rfvOneDimSigToNoise.Text = revTauC.Text = Sanitar.OneDimSigToNoise.ErrorMsg;
            tbOneDimSigToNoise.MaxLength = Sanitar.OneDimSigToNoise.MaxLength;

            // ALISTAIR 24/05/2011:
            // Replaced increments with spectral widths validation
            tbSpectralWidths.MaxLength = 50;
            revSpectralWidths.ValidationExpression = @"^([\da-zA-Z]+\w* *= *(\d*(\.)\d*|0?(\.)\d*[1-9]\d*|[1-9]\d*) *; *)+$";
            //revSpectralWidths.ValidationExpression = @"^([\da-zA-Z]+\w* *= *([1-9]\d*) *; *)+$";
            revSpectralWidths.Text = "Usage: 'Name = value;' name refers to the 'dimension' attribute in benchmarks setup. Semicolon is obligatory.";

            // ALISTAIR 10/04/2012: Add validation for Spectrometer Frequency
            tbSpectFreq.MaxLength = 50;
            revSpectFreq.ValidationExpression = @"^([\da-zA-Z]+\w* *= *(\d*(\.)\d*|0?(\.)\d*[1-9]\d*|[1-9]\d*) *; *)+$";
            //revSpectralWidths.ValidationExpression = @"^([\da-zA-Z]+\w* *= *([1-9]\d*) *; *)+$";
            revSpectFreq.Text = "Usage: 'Name = value;' name refers to the 'dimension' attribute in benchmarks setup. Semicolon is obligatory.";

            // ALISTAIR 26/03/2012:
            // Same validation as for spectral widths
            tbNoOfPointsSI.MaxLength = 50;
            revNoOfPointsSI.ValidationExpression = @"^([\da-zA-Z]+\w* *= *([1-9]\d*) *; *)+$";
            // each value must be a power of two
            //if ((Math.Log(bS.NoOfPointsSI, 2) % 1) != 0)
            revNoOfPointsSI.Text = "Usage: 'Name = value;' name refers to the 'dimension' attribute in benchmarks setup. Number of points should be a power of 2, e.g. 15N=1024; (where 1024=2^10).Semicolon is obligatory.";

            // ALISTAIR 24/05/2011
            // TODO: Need to change usage to something more intuitive
            // i.e. NOT 'Name = value;'; format
            tbTimeDomains.MaxLength = 50;
            revTimeDomains.Text = "Usage: 'Name = value;' name refers to the 'dimension' attribute in benchmarks setup. Semicolon is obligatory.";
            revTimeDomains.ValidationExpression = @"^([\da-zA-Z]+\w* *= *([1-9]\d*) *; *)+$";

            // ALISTAIR 26/03/2012:
            // Added validation for number of transients (NS)
            /*
            tbNoOfTransients.MaxLength = 50;
            revNoOfTransients.ValidationExpression = @"^([1-9]\d*)+$";
            revNoOfTransients.Text = "Usage: No of transients must be a positive integer";
            */

            // ALISTAIR 06/07/2011: Validate spinach macro file name
            // Used experiment name validation as a template
            tbSpinachMacroName.MaxLength = Sanitar.Name.MaxLength;
            rfvSpinachMacroName.Text = revSpinachMacroName.Text = Sanitar.Name.ErrorMsg;
            revSpinachMacroName.ValidationExpression = Sanitar.Name.ValidationRegex;

        }

        // ALISTAIR: Comments added 09/07/2011
        // Retrieve info from the web form
        // Called by: btnGenerate_Click
        protected override void CollectFormData()
        {
            // inherit form data info from IdeaNMR.Web.DesignNMRObject_BasePage class,
            // which in turn inherits from IdeaNMR.Web.DesignBaseObject_BasePage class
            // experiment name given by user
            // "remarks" text given by user
            // contents of the "scipt" tab entered by the user
            // version info specified by the user
            // contents of the "pulse program" tab entered by the user
            // list of authors associated with the pulse program
            base.CollectFormData();

            // get the type of pulse program entered by the user
            this.PulseProgramInfo.EntityObject.Type = this.tbType.Text;
            // get the dimensionality entered by the user
            this.PulseProgramInfo.EntityObject.Dimensionality = (byte)(this.ddlDimensionality.SelectedIndex - 1);
            // get the contents of the "Simulated FIDs" tab entered by the user
            this.PulseProgramInfo.EntityObject.Analytics = this.tbAnalytics.Text;


            // get the contents of the "Spinach Macro" tab entered by the user
            this.PulseProgramInfo.EntityObject.SpinachMacro = this.tbSpinachMacro.Text;


            // gets the current list of "components"
            // i.e. "wave shapes", "composite pulse decouplings (CPDs)" and "scripts"
            this.PulseProgramInfo.EntityObject.AuxComponents.Clear();
            foreach (NamedComponent nC in this.ComponentsList.Components)
            {
                NamedComponent c = new NamedComponent();
                c.AuxComponent = nC.AuxComponent;
                c.Id = nC.Id;
                c.Identifier = nC.Identifier;
                this.PulseProgramInfo.EntityObject.AuxComponents.Add(c);
            }
        }

        protected override void UpdateFormData()
        {
            base.UpdateFormData();

            this.tbType.Text = this.EntityFacade.EntityObject.Type;
            this.ddlDimensionality.SelectedIndex = this.EntityFacade.EntityObject.Dimensionality + 1;
            this.tbAnalytics.Text = string.IsNullOrWhiteSpace( this.EntityFacade.EntityObject.Analytics)
                                        ? EmbeddedPythonScripts.LoadScript("Analytics.xml")
                                        : this.EntityFacade.EntityObject.Analytics;

            this.ComponentsList.Components = this.PulseProgramInfo.EntityObject.AuxComponents.ToList();
            
            // ALISTAIR 25/08/2011
            // Copy the Spinach Macro details to the appropriate textbox
            this.tbSpinachMacro.Text = this.EntityFacade.EntityObject.SpinachMacro;

            // Get Selected spectrometer and Sample cookies
            HttpCookie sampCookie = Request.Cookies[default_sample_cookie];
            if (sampCookie != null)
                SetSample(IdeaNMRdbManager.GetHardObject<Sample>(sampCookie.Value, ScrewTurnWikiFacade.Username));

            HttpCookie specCookie = Request.Cookies[default_spectrometer_cookie];
            if (specCookie != null)
                SetSpectrometer(IdeaNMRdbManager.GetHardObject<Spectrometer>(specCookie.Value, ScrewTurnWikiFacade.Username));
        }

        private void WriteMessage(string msg, LogBookEntryType msgType)
        {
            System.Drawing.Color c;
            switch (msgType)
            {
                case LogBookEntryType.Status:
                    c = System.Drawing.Color.YellowGreen;
                    break;
                case LogBookEntryType.Checkpoint:
                    msg = msg.ToUpper();
                    c = System.Drawing.Color.Black;
                    break;
                case LogBookEntryType.Error:
                    c = System.Drawing.Color.Red;
                    break;
                case LogBookEntryType.Warning:
                    c = System.Drawing.Color.Orange;
                    break;
                case LogBookEntryType.General:
                    c = System.Drawing.Color.Black;
                    break;
                default:
                    c = System.Drawing.Color.Black;
                    break;
            }

            Label lMSG = new Label();
            lMSG.ForeColor = c;
            lMSG.Text = msg;

            StringBuilder sb = new StringBuilder();
            HtmlTextWriter httw = new HtmlTextWriter(new System.IO.StringWriter(sb));
            lMSG.RenderControl(httw);
            string newLine = "<br />";
            this.LogString += sb.ToString();
            this.LogString += newLine;
        }

        /// <summary>
        /// Parses Source than must be in a form AAA = ffff; where AAA - name, ffff - value and puts it
        /// into the Dictionary<AAA, ffff>.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="Source"></param>
        /// <returns></returns>
        private Dictionary<string, T> ReadParameters<T>(string Source)
            where T : struct
        {
            Dictionary<string, T> result = new Dictionary<string, T>();
            string[] pars_vals = Source.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
            foreach (string par_val in pars_vals)
            {
                string[] pv = par_val.Split('=');
                string param = pv[0].Trim();
                string val = pv[1].Trim();

                T v = (T)Convert.ChangeType(val, typeof(T));

                if (result.ContainsKey(param))
                {
                    string msg = string.Format("Parametes string contains parameter {0} twice.", param);
                    result[param] = v;
                }
                else
                {
                    result.Add(param, v);
                }
            }

            return result;
        }

        // ALISTAIR: COMMENT ADDED 24/01/2011
        // Displays all output from the processing of the benchmark data
        // This is initiated after the "Show Benchmarks Data" link is selected
        // (via vBenchmarks_PreRender(), see above)
        // Note: The display properties of most results are handled by a Cascading Style Sheet class (CssClass)
        // CALLED BY: vBenchmarks_PreRender()
        // CALLING: none
        private void DisplayBenchmarkData()
        {
            // ALISTAIR: COMMENT ADDED 24/01/2011
            // Traverse through the list of Benchmark results,
            // displaying the results for each dimension

            // ALISTAIR 23/05/2011
            // New iterator for BenchmarkResults loop
            int i = 0;

            foreach (BenchmarkResult bR in this.BenchmarkResults)
            {
                // increment iterator
                i = i + 1;

                // ALISTAIR: COMMENT ADDED 24/01/2011
                // Create a new Panel object, onto which the Benchmark results
                // for the current dimension are added
                Panel main = new Panel();
                main.CssClass = "DesignPulseProgram_ASPX BenchmarkResult Container";
                main.Style.Add(HtmlTextWriterStyle.Position, "relative");

                // ALISTAIR: MODIFIED 24/01/2011
                // Change label from dimension to tauC value
                //dim.Text = string.Format("DIMENSION: {0}", bR.Dimension);
                //dim.CssClass = "DesignPulseProgram_ASPX BenchmarkResult Dimension";
                //main.Controls.Add(dim);
                Label tauC = new Label();

                // There is a separate BenchmarkResult in each indirect dimension
                // for the user's selected TauC rotational correlation time.
                // These are at the start of the results array, so the first
                // results for the number of indirect dimensions should display appropriately
                if (i <= indirectDimensions)
                {
                    // ALISTAIR added 24/09/2012
                    // Diagnostic output for debugging
                    //String diagnosticOutput = "TauC: " + bR.TauC.ToString() + "\n";
                    //System.Diagnostics.Debug.WriteLine(diagnosticOutput);

                    tauC.Text = string.Format("Dimension: {0}, User's TauC: {1}", bR.Dimension, tbTauC.Text);
                }
                else
                {
                    // ALISTAIR added 24/09/2012
                    // Diagnostic output for debugging
                    //String diagnosticOutput = "TauC: " + bR.TauC.ToString() + "\n";
                    //System.Diagnostics.Debug.WriteLine(diagnosticOutput);

                    tauC.Text = string.Format("TauC: {0}", bR.TauC.ToString());
                }

                tauC.CssClass = "DesignPulseProgram_ASPX BenchmarkResult TauC";
                main.Controls.Add(tauC);

                // ALISTAIR: COMMENT ADDED 24/01/2011
                // Create another new Panel object, onto which specific values are added
                Panel vals = new Panel();
                vals.CssClass = "DesignPulseProgram_ASPX BenchmarkResult Values";
                vals.Style.Add(HtmlTextWriterStyle.Display, "inline-block");

                Label td = new Label();
                td.Text = string.Format("<span class=\"Fields\">TD:</span> <b>{0}</b>", bR.TimeDomain);
                td.CssClass = "DesignPulseProgram_ASPX BenchmarkResult TimeDomain";
                vals.Controls.Add(td);

                Label sig = new Label();
                sig.Text = string.Format("<span class=\"Fields\">Max Signal:</span> <b>{0:0.000}+/-{1:0.000}</b>", bR.Signal.Value, bR.Signal.Error);
                sig.CssClass = "DesignPulseProgram_ASPX BenchmarkResult MaxSignal";
                vals.Controls.Add(sig);

                Label noise = new Label();
                noise.Text = string.Format("<span class=\"Fields\">Noise RMSD:</span> <b>{0:0.000}+/-{1:0.000}</b>", bR.Noise.Value, bR.Noise.Error);
                noise.CssClass = "DesignPulseProgram_ASPX BenchmarkResult Noise";
                vals.Controls.Add(noise);

                Label line = new Label();
                line.Text = string.Format("<span class=\"Fields\">Linewidth (Hz):</span> <b>{0:0.000}+/-{1:0.000}</b>", bR.Linewidth.Value, bR.Linewidth.Error);
                line.CssClass = "DesignPulseProgram_ASPX BenchmarkResult Linewidth";
                vals.Controls.Add(line);

                Label sn = new Label();
                sn.Text = string.Format("<span class=\"Fields\">S/N:</span> <b>{0:0.000}+/-{1:0.000}</b>", bR.SignalToNoise.Value, bR.SignalToNoise.Error);
                sn.CssClass = "DesignPulseProgram_ASPX BenchmarkResult SignalToNoise";
                vals.Controls.Add(sn);

                Label sntd = new Label();
                sntd.Text = string.Format("<span class=\"Fields\">S/N/TD:</span> <b>{0:0.000}</b>", bR.StNtTD);
                sntd.CssClass = "DesignPulseProgram_ASPX BenchmarkResult StNtTD";
                vals.Controls.Add(sntd);

// ALISTAIR 27/03/2012:
// Temporarily add additional values, for testing purposes
/*
Label sn1d = new Label();
sn1d.Text = string.Format("<br/><span class=\"Fields\">S/N from 1D:</span> <b>{0:0.000}</b>", bR.StoNfrom1D);
sn1d.CssClass = "DesignPulseProgram_ASPX BenchmarkResult StNtTD";
vals.Controls.Add(sn1d);
Label SI = new Label();
SI.Text = string.Format("<span class=\"Fields\">SI points:</span> <b>{0:0.000}</b>", bR.SI);
SI.CssClass = "DesignPulseProgram_ASPX BenchmarkResult StNtTD";
vals.Controls.Add(SI);
Label noOfScans = new Label();
noOfScans.Text = string.Format("<span class=\"Fields\">Transients:</span> <b>{0:0.000}</b>", bR.NoOfScans);
noOfScans.CssClass = "DesignPulseProgram_ASPX BenchmarkResult StNtTD";
vals.Controls.Add(noOfScans);
Label snDivRootTD = new Label();
snDivRootTD.Text = string.Format("<span class=\"Fields\">S/N/Sqrt(TD):</span> <b>{0:0.000}</b>", bR.StoNdivRootTD);
snDivRootTD.CssClass = "DesignPulseProgram_ASPX BenchmarkResult StNtTD";
vals.Controls.Add(snDivRootTD);
*/
                // ALISTAIR: COMMENT ADDED 24/01/2011
                // now add the values panel (vals) to the "main" panel
                main.Controls.Add(vals);

                // ALISTAIR: COMMENT ADDED 24/01/2011
                // Display the 1D spectrum illustrating s/n in the current dimension.
                // Uses the LoadControl method to dynamically add the spectrum to the "main" Panel
                DisplayBenchmark spectrum = (DisplayBenchmark)LoadControl("DisplayBenchmark.ascx");
                spectrum.Dimension = bR.Dimension;
                spectrum.Data = bR.Spectrum;
                spectrum.CssClass = "DesignPulseProgram_ASPX BenchmarkResult Spectrum";
                spectrum.Width = 550; spectrum.Height = 300;
                main.Controls.Add(spectrum);

                // ALISTAIR added 25/09/2012
                // Diagnostic output for debugging
                /*
                // include if statement if only want output for user tauC
                if (i <= indirectDimensions)
                {
                    String diagnosticOutput = "TauC: " + bR.TauC.ToString() + "\n";
                    diagnosticOutput += "Points in Spectrum:\n";
                    diagnosticOutput += "Freq(Hz),Amplitude\n";

                    foreach (Point p in spectrum.Data)
                    {
                        diagnosticOutput += p.T.ToString() + "," + (double)p.V.Real + "\n";
                    }
                    System.Diagnostics.Debug.WriteLine(diagnosticOutput);
                }
                */

                // ALISTAIR: COMMENT ADDED 24/01/2011
                // display execution time in seconds to three decimal places
                Label time = new Label();
                time.Text = string.Format("<br /><i>Done in {0:s\\.fff} seconds</i>", bR.ExecutionTime);
                main.Controls.Add(time);

                LiteralControl lt = new LiteralControl(@"<hr />");
                main.Controls.Add(lt);

                // ALISTAIR: COMMENT ADDED 24/01/2011
                // PlaceHolder control
                // adds the "main" panel to the position of the phBenchmarks PlaceHolder in the ASP 
                phBenchmarks.Controls.Add(main);

            }
        }
    } 
}